/*
分支切换的意义

以下代码

const data = {ok: true, text: "hello"}
const obj = new Proxy(data, ...)

effect(function effectFn() {
  // 这里存在一个三元表达式, 根据字段ok的值来执行不同的分支
  document.body.innerText = obj.ok ? obj.text : 'Not Ok'
})

此时的依赖关系为:
-data
---ok: [effectFn]
---text: [effectFn]

ok和text的effect函数都是同一个，但是若ok由true->false后，理想状态下不管text怎么变化，都不应该再触发effect执行了，
因为document.body.innerText就是死的"Not Ok"，但是实际情况就是text变化，还是会触发effect函数执行(遗留的effect函数会产生不必要的更新)
*/

const bucket = new WeakMap()

// 解决思路，每次effect函数执行时，先把它从所有与之相关的依赖结合中删除
let activeEffect
function effect(fn) {
  const effectFn = () => {
    // 调用cleanup，清除遗留的effect函数
    cleanup(effectFn)
    activeEffect = effectFn
    fn()
  }
  // activeEffect.deps用来存储所有与之关联的deps集合
  effectFn.deps = []
  effectFn()
}

// 清除遗留effect函数
function cleanup(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i] // deps是Set，就是effectFn的对应的target.key的所有的effect函数结合
    deps.delete(effectFn)
  }
  effectFn.deps.length = 0 // 清空effectFn的deps数组
}

function track(target, key) {
  if (!activeEffect) return
  let depsMap = bucket.get(target)
  if (!depsMap) bucket.set(target, (depsMap = new Map()))
  let deps = depsMap.get(key)
  if (!deps) depsMap.set(key, (deps = new Set()))
  deps.add(activeEffect) // 依赖中添加activeEffect, 这里说明下，deps是target的key对应的所有effect函数集合
  activeEffect.deps.push(deps) // activeEffect的deps数组中 保存当前所有依赖，方便effect函数执行完毕后删除
}

function trigger(target, key) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  const deps = depsMap.get(key)
  // deps && deps.forEach((effectFn) => effectFn()) // 这一句有问题，会造成死循环
  /*
  因为effectFn其实就是activeEffect, 先cleanup, 相当于从这个deps中删除了这个effectFn
  然后又因为fn的执行, 又触发了track的收集(deps.add), 所以循环不会停止
  解决方法是，把set数据弄到另外第一个新的set内
  */
  const effectsToRun = new Set(deps)
  effectsToRun.forEach((effectFn) => effectFn())
}

const data = { text: 'hello world', ok: true }
const obj = new Proxy(data, {
  get(target, key) {
    track(target, key)
    return Reflect.get(target, key)
  },
  set(target, key, val) {
    Reflect.set(target, key, val)
    trigger(target, key)
    return true
  },
})

effect(function listenOkAndText() {
  console.count('effects with cleanup')
  document.querySelector('#app').innerText = obj.ok ? obj.text : 'Not Ok'
})
effect(function listenText() {
  document.querySelector('#app2').innerText = obj.text
})

window.tObj = obj
